<?php
/**
 * Created by PhpStorm.
 * User: LiuJiaJun
 * Date: 2019/1/7
 * Time: 13:31
 */

namespace api\storage\service\output;

use api\storage\common\Constant;
use api\storage\common\FileDataTemplate;
use api\storage\exception\OutOfBoundException;
use api\storage\model\BomDetailModel;
use api\storage\model\BomModel;
use api\storage\model\InflowModel;
use api\storage\model\OutflowModel;
use api\storage\model\ReelsModel;
use api\storage\model\SeatModel;
use api\storage\model\SmtMaterielModel;
use api\storage\model\StorageModel;
use api\storage\model\SupplierMaterielModel;
use api\storage\model\SupplierModel;
use api\storage\service\BaseService;
use api\storage\service\BomService;
use api\storage\service\CarService;
use api\storage\service\InflowService;
use api\storage\service\OutflowService;
use api\storage\service\SocketService;
use api\storage\service\StorageService;
use service\file\CommonFile;
use service\file\FileLock;
use service\logger\LoggerFactory;
use think\Debug;
use think\Exception;
use think\model\Collection;

abstract class BaseOutputService extends BaseService
{
    protected $bomModel;
    protected $bomDetailModel;
    protected $logger;
    protected $file;
    protected $smtSupplier;
    protected $isComposite;
    protected $storageUuid;
    protected $smtMaterielModel;
    /**
     * 切割reelId起始的位置
     */
    const REEL_ID_SLIDE_START = 0;
    /**
     * 切割reelId起始的长度
     */
    const REEL_ID_SLIDE_LEN = 12;
    const BOM_STATUS_CREATED = 'created';
    const BOM_STATUS_CONFIRMED = 'confirmed';
    const BOM_STATUS_PUTTING = 'putting';
    const BOM_STATUS_COMPLETED = 'completed';

    public function __construct()
    {
        $this->bomModel = new BomModel();
        $this->bomDetailModel = new BomDetailModel();
        $this->smtSupplier = 'D0592-283';
        $this->smtMaterielModel = new SmtMaterielModel();
        $this->logger = LoggerFactory::getLogger('output');


    }

    /**
     * 出库单出库获取信息
     * 1. 初始化文件信息
     * 2. 查看这个仓库是否有出库单在出库，如果是，查看是否是同一个出库单，同一个出库单，返回文件数据
     * 3. 查看出库单的状态，如果是欠料，获取欠料信息，如果是新单，直接获取新单信息
     * 4. 处理特殊情况，没有需要出库的单，没有需要发实物的单
     * 5. 判断出库单是否是特殊的出库单，如果是，直接从特殊文件中获取数据
     * 6. 根据物料号来获取具体的reelId
     * 7. 进行数据处理、包装
     * 8. 记录文件，返回数据
     * @param $bomId
     * @param $bom
     * @param $supplierId
     * @param $storageUuid
     * @param $isComposite
     * @return array
     * @throws Exception
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function getBomData($bomId, $bom, $supplierId, $storageUuid, $isComposite)
    {
        Debug::remark("getBomDataStart");
        $this->logger->debug("获取物料==========开始=============");
        $this->initFile($bomId, $storageUuid);
        $this->isComposite = $isComposite;
        $storageModel = new StorageModel();
        $storage = $storageModel->findStorageByUuid($storageUuid);
        $bomUuid = $storage->getData('bom_uuid');
        if ($this->isNotEmpty($bomUuid)) {
            //如果相等，那么就是已经存在文件，已经有人正在出库了
            if ($bomUuid == $bom->getData('uuid')) {
                //返回已经存在的出库文件
                $tmpData = $this->jsonDecode($this->file->read());
                $this->logger->debug("出库单：{$bomId}进入多人出库模式");
                return $this->resultTemplate(2, $this->returnTemplate($tmpData['performData'], $tmpData['batchData'], $tmpData['lackData'], $tmpData['feedData']));
            } else {
                $this->logger->warn("出库单：{$bomId}的uuid与数据库中的bomUuid({$bomUuid})不一致");
                //当前的出库单和正在出库的出库单不一致
                return $this->resultTemplate(-2, $this->returnTemplate());
            }
        }
        //处理特殊的Bom单
        if ($this->isSpecialBom($bomId, $bom->getData('operate_type'))) {
            $this->logger->debug("出库单：{$bomId}是特殊的单");
            return $this->handleSpecialBomData($bomId, $bom->getData('uuid'), $storageUuid);
        }
        //处理订单状态 新建 or 欠料
        if ($bom->getData('status') == 2 || $bom->getData('status') == 3) {
            $this->logger->debug("出库单：{$bomId}是未完成的单");
            $data = $this->getLackMateriel($bom->getData('uuid'), $supplierId);
        } else {
            $this->logger->debug("出库单：{$bomId}是新单");
            $data = $this->getBomMateriel($bom->getData('uuid'), $supplierId);
        }
        //判断订单的情况 订单为空 or 不需要发实物
        if (empty($data['materielData']) && empty($data['specialData'])) {
            $this->logger->info("出库单：{$bomId}没有物料信息");
            return $this->resultTemplate(-1);
        } else if (empty($data['materielData']) && !empty($data['specialData'])) {
            $this->logger->info("出库单：{$bomId}不需要出实物");
            $this->file->write($this->fileTemplate([], [], [], [], [], $data['specialData'], [], true));
            $this->complete($bomId, $storageUuid);
            return $this->resultTemplate(3, $this->returnTemplate());
        }
        return $this->handleCommonBomData($bomId, $bom->getData('uuid'), $data['materielData'], $data['recordData'], $data['specialData'], $storageUuid);
    }

    /**
     * 确认出库
     * 1. 初始化文件信息
     * 2. 判断仓库的bomUuid字段是否和bom单的相同，不同，直接报错
     * 3. 读物文件内容，如果没有文件内容，直接返回
     * 4. 判断文件内容的person字段，如果不存在，则设置该字段，并且亮灯
     * 5. 最后再判断仓库的字段是否占用，如果没有，则占用
     * 6. 返回数据
     *
     * @param $bomId
     * @param $storageUuid
     * @param $nickname
     * @param $carIds
     * @return array
     * @throws Exception
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function confirm($bomId, $storageUuid, $nickname, $carIds = [])
    {
        $this->logger->debug("用户：{$nickname},bomId：{$bomId}确认出库操作");
        $this->initFile($bomId, $storageUuid);
        $bomInfo = BomService::getBomInfo($bomId);
        $storageModel = new StorageModel();
        $storage = $storageModel->findStorageByUuid($storageUuid);
        $bomUuid = $storage->getData('bom_uuid');
        if ($bomUuid == $bomInfo->getData('uuid')) {
            $result = $this->jsonDecode($this->file->read());
            //第一个人确定出库，要亮红灯
            if ($result['status'] == self::BOM_STATUS_CREATED) {
                $result['status'] = self::BOM_STATUS_CONFIRMED;
                SocketService::send($result['seatIds'], $storageUuid, Constant::TURN_ON_RED);
                if (!empty($carIds)) {
                    //如果是边出库边装车需要进行预获取站位
                    $carService = new CarService();
                    $this->logger->debug("用户：{$nickname}，bomId：{$bomId}进行小车站位的分配");
                    $carService->distributeSeatCode($carIds, $bomId, 1, 1, $storageUuid);
                }
            }
            $result['person'] = $result['person'] + 1;
            $this->file->write($result);
            //如果没有物料需要出库，将出库文件备份后，删除
            if (empty($result['performData']) && empty($result['batchData'])) {
                $this->logger->debug("用户：{$nickname}，bomId：{$bomId}没有需要出库的物料。那么将仓库字段清除");
                StorageService::setStorageWithBomUuid('', $storageUuid);
                $filePath = Constant::RECORD_BACKUP_OUTPUT . $bomId . Constant::RECORD_FILE_SUFFIX;
                $backFile = new CommonFile($storageUuid, $filePath);
                $backFile->appendWrite($result);
                $this->file->delete();
                return $this->resultTemplate(3, $this->returnTemplate());
            }
            return $this->resultTemplate(1, $this->returnTemplate($result['performData'], $result['batchData'], $result['lackData'], $result['feedData']));

        }
        $this->logger->error("用户：{$nickname}，storage中的bomUuid: {$bomUuid},bom中的uuid: {$bomInfo->getData('uuid')}");
        return $this->resultTemplate(0, $this->returnTemplate());
    }

    /**
     * 取消出库
     * 1. 初始化文件信息
     * 2. 判断仓库的bomUuid字段是否和bom单的相同，不同，直接报错
     * 3. 清除仓库占用字段，删除文件
     * 4. 返回数据
     *
     * @param $bomId
     * @param $storageUuid
     * @param $nickname
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function cancel($bomId, $storageUuid, $nickname)
    {
        $this->logger->debug("用户：{$nickname},bomId：{$bomId}取消出库操作");
        $this->initFile($bomId, $storageUuid);
        $bomInfo = BomService::getBomInfo($bomId);
        $storageModel = new StorageModel();
        $storage = $storageModel->findStorageByUuid($storageUuid);
        $bomUuid = $storage->getData('bom_uuid');
        if ($bomUuid == $bomInfo->getData('uuid') || empty($bomUuid)) {
            $result = $this->jsonDecode($this->file->read());
            if ($result['status'] == self::BOM_STATUS_CREATED) {
                $this->logger->debug("清除仓库：{$storage->getData('name')}的bomUuid字段：{$bomUuid}");
                StorageService::setStorageWithBomUuid('', $storageUuid);
                $this->logger->debug("删除出库文件");
                $this->file->delete();
                return $this->resultTemplate(1);
            } else {
                $this->logger->debug("已经确认过出库的出库单不能进行删除数据处理，PDA允许退出出库操作");
                return $this->resultTemplate(-1);
            }

        } else {
            $this->logger->info("仓库的bomUuid：{$bomUuid},bom中的uuid：{$bomInfo->getData('uuid')}");
            return $this->resultTemplate(0);
        }
    }

    /**
     * 检测bom单的状态
     * 1. 初始化文件信息
     * 2. 判断文件内容是否存在
     * 3. 返回数据
     *
     * @param $bomId
     * @param $storageUuid
     * @param $nickname
     * @return array
     */
    public function checkBomStatus($bomId, $storageUuid, $nickname)
    {
        $this->initFile($bomId, $storageUuid);
        $data = $this->jsonDecode($this->file->read());
        if (empty($data)) {
            $this->logger->info("用户：{$nickname}，检测出库单：{$bomId}不存在，即为备料完成");
            return $this->resultTemplate(1);
        } else {
            return $this->resultTemplate(0);
        }
    }

    /**
     * 检查物料信息
     * 1. 初始化文件信息
     * 2. 查看reelId是否在数据库中
     * 3. 查看reelId是否在出库的盘料中
     * 4. 查看reelId是否在出库的散料中
     * 5. 如果不在盘料或者散料中，则这个料是错料处理
     * 6. 返回数据
     *
     * @param $bomId
     * @param $reelId
     * @param $storageUuid
     * @return array
     * @throws Exception
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function checkReels($bomId, $reelId, $storageUuid)
    {
        $this->initFile($bomId, $storageUuid);
        $reelsModel = new ReelsModel();
        $reelsInfo = $reelsModel->findReelByIdAndUuid($reelId, $storageUuid);
        if ($this->isEmpty($reelsInfo)) {
            return $this->resultTemplate(-1);
        }
        $data = $this->jsonDecode($this->file->read());
        $materielId = $reelsInfo->getData('sub_materiel_id');
        if (isset($data['recordData'][$materielId])) {
            $tmpMaterielData = $data['recordData'][$materielId];
            $result['planCount'] = isset($tmpMaterielData) ? $tmpMaterielData['planCount'] : 0;
            $result['currentCount'] = isset($tmpMaterielData) ? $tmpMaterielData['currentCount'] : 0;
            $result['count'] = $reelsInfo->getData('count');
            $result['materielId'] = $materielId;
            $result['seatCode'] = $reelsInfo->getData('seat_code');
            $result['containerCode'] = $reelsInfo->getData('container_code');
            //先查找站位上的物料
            $reelIds = array_column($data['performData'], 'reelId');
            if (in_array($reelId, $reelIds)) {
                return $this->resultTemplate(1, $result);
            }
            //在查找容器上的物料
            $batchId = substr($reelId, self::REEL_ID_SLIDE_START, self::REEL_ID_SLIDE_LEN);
            foreach ($data['batchData'] as $datum) {
                if ($batchId == $datum['batchId'] && $reelsInfo->getData('container_code') == $datum['containerCode']) {
                    return $this->resultTemplate(1, $result);
                }
            }
        }
        //如果都没找到的话。那就是错料了
        $wrongResult['seatCode'] = $reelsInfo->getData('seat_code');
        $container_code = $reelsInfo->getData('container_code');
        $containerCode = empty($container_code) ? null : $reelsInfo->getData('container_code');
        $wrongResult['containerCode'] = $containerCode;
        $seatModel = new SeatModel();
        $seatId = $seatModel->findSeatIdByCodeAndStorageUuid($reelsInfo->getData('seat_code'), $storageUuid);
        SocketService::send($seatId, $storageUuid, Constant::TURN_ON_GREEN);
        return $this->resultTemplate(2, $wrongResult);
    }

    /**
     * 物料出库
     * 1. 查看reelId是否在数据库中
     * 2. 加文件锁
     * 3. 查看该物料是否已经出足够了，防止多出
     * 4. 判断出库的数量，如果出的少那么会存在有余料的情况，需要余料归还
     * 5. 无论如何，都需要修改reel的数据，站位情况，以及出库流水
     * 6. 修改文件的内容，判断物料是否已经完成，容器是否应该灭灯等等，具体看代码
     * 7. 到最后有三种情况
     *    1）需要归还物料   2）不需要归还物料  3）不需要归还物料并且这个物料是最后一个料
     * 8. 返回数据
     *
     * @param $bomId
     * @param $reelId
     * @param $currentCount
     * @param $originalCount
     * @param $storageUuid
     * @param $nickname
     * @return array
     * @throws Exception
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     * @throws \think\exception\PDOException
     * @throws \Exception
     */
    public function outputReels($bomId, $reelId, $currentCount, $originalCount, $storageUuid, $nickname)
    {
        $reelsModel = new ReelsModel();
        $reelInfo = $reelsModel->findReelByIdAndUuid($reelId, $storageUuid);
        if (empty($reelInfo) || $reelInfo->getData('status') == 0) {
            return $this->resultTemplate(-2, null);
        }
        $lock = new FileLock($storageUuid);
        $lock->lock(FileLock::LOCK_TYPE_OUTPUT);
        $this->initFile($bomId, $storageUuid);
        $data = $this->jsonDecode($this->file->read());
        //查看物料是否已经出够了
        $tmpResult = $this->isEnoughMateriel($reelId, $reelInfo->getData('container_code'), $data);
        if ($tmpResult['status'] <= 0) {
            return $tmpResult;
        }
        $seatCode = $reelInfo->getData('seat_code');
        $materielId = $reelInfo->getData('sub_materiel_id');
        $containerCode = $reelInfo->getData('container_code');
        $seatModel = new SeatModel();

        $seatId = $seatModel->findSeatIdByCodeAndStorageUuid($seatCode, $storageUuid);
        $reelsModel->startTrans();
        $leftMateriel = null;
        $seatRes = true;
        list($orderId, $fentryId) = BomService::judgeBomType($bomId);
        $this->logger->debug("用户：{$nickname}，bomId：{$bomId}，reelId：{$reelId}，materielId：{$materielId}，seatCode：{$seatCode}，拥有数据：{$originalCount}，出库数量：{$currentCount}");
        //修改物料数据
        if ($originalCount === $currentCount) {
            $reelsRes = $reelsModel->deleteByUuidList($reelInfo->getData('uuid'));
            $seatRes = $seatModel->updateBatchNotUsed($seatCode, $storageUuid);
        } else if ($originalCount > $currentCount) {
            $leftCount = $originalCount - $currentCount;
            $reelsRes = $reelsModel->updateReel($reelId, $storageUuid, ['count' => $leftCount]);
            $leftMateriel = [
                'seatCode' => $seatCode,
                'containerCode' => $containerCode,
                'count' => $leftCount
            ];
        } else {
            throw new OutOfBoundException("要出库的数量不能大于原本数量");
        }
        //添加出库流水
        $outflow = OutflowService::generateOutflow($reelId, $currentCount, $materielId, $orderId, $storageUuid, $nickname, '', $fentryId);
        $outflowModel = new OutflowModel();
        $outflowRes = $outflowModel->add($outflow);
        /*****
         * 修改smt_materiel_id表的数据
         */

        if($storageUuid == "184928afb9e35f3ff421da05fc3f917f"){//如果是smt出库
            $this->smtMaterielModel->startTrans();
            $smtMaterielObj = $this->smtMaterielModel->findByLock(['sub_materiel_id' => $outflow['sub_materiel_id']]);

                $smtMateriel = $smtMaterielObj->toArray();
                $smtData = [
                    'total_num' => $smtMateriel['total_num'] - $outflow['count'],
                    'simulation_num' => $smtMateriel['simulation_num'] - $outflow['count'],
                ];
                $this->smtMaterielModel->updateMaterielWithLock($smtData,['sub_materiel_id' => $outflow['sub_materiel_id']]);
                $this->smtMaterielModel->updateCheckTime(['sub_materiel_id' => $outflow['sub_materiel_id']]);
                $this->smtMaterielModel->commit();
            }


        //如果操作数据库没有成功的话，将数据回滚
        if (empty($reelsRes) || $seatRes === false || empty($outflowRes)) {
            $lock->unlock();
            $reelsModel->rollback();
            return $this->resultTemplate(0);
        }
        $reelsModel->commit();
        $data['recordData'][$materielId]['currentCount'] += $currentCount;
        if (empty($containerCode)) {
            //盘料处理
            if (empty($leftMateriel)) {
                SocketService::send($seatId, $storageUuid, Constant::TURN_OFF_LIGHT);
            } else {
                SocketService::send($seatId, $storageUuid, Constant::TURN_ON_GREEN);
            }
            //找出这个物料的位置
            $reelIds = array_column($data['performData'], 'reelId');
            $index = array_search($reelId, $reelIds);
            //给这个物料加数量，并且判断这个物料是否完成
            $data['performData'][$index]['tempCount'] += $currentCount;
            if ($data['performData'][$index]['tempCount'] >= $data['performData'][$index]['count'] ||
                $data['recordData'][$materielId]['currentCount'] >= $data['recordData'][$materielId]['planCount']) {
                $data['performData'][$index]['isFinished'] = true;
            }
        } else {
            //容器处理
            $batchId = substr($reelId, self::REEL_ID_SLIDE_START, self::REEL_ID_SLIDE_LEN);
            foreach ($data['batchData'] as &$item) {
                if ($batchId == $item['batchId'] && $containerCode == $item['containerCode']) {
                    $item['tempCount'] += $currentCount;
                    if ($item['tempCount'] >= $item['count']) {
                        $item['isFinished'] = true;
                    }
                    //如果出库数量够了的话，那么就将这个物料所在的所有容器的状态设置为完成
                    if ($data['recordData'][$materielId]['currentCount'] >= $data['recordData'][$materielId]['planCount']) {
                        foreach ($data['batchData'] as &$it) {
                            if ($materielId == $it['materielId'] && $it['isFinished'] == false) {
                                $this->logger->debug("物料：{$materielId}在容器中出库完成，将批次号{$it['batchId']}设为完成");
                                $it['isFinished'] = true;
                                //也要把该位置的灯给灭了
                                $tmpSeatId = $seatModel->findSeatIdByCodeAndStorageUuid($it['seatCode'], $storageUuid);
                                SocketService::send($tmpSeatId, $storageUuid, Constant::TURN_OFF_LIGHT);
                            }
                        }
                    }
                    break;
                }
            }
            //判断这个容器里的物料是否全部出库完成，全部出库完成的话需要灭灯
            $containerCodes = array_column($data['batchData'], 'containerCode');
            $index = array_search($containerCode, $containerCodes);
            if ($data['batchData'][$index]['isFinished']) {
                if ($leftMateriel) {
                    SocketService::send($seatId, $storageUuid, Constant::TURN_ON_GREEN);
                } else {
                    SocketService::send($seatId, $storageUuid, Constant::TURN_OFF_LIGHT);
                }
            }
        }
        $flag = $this->isFinished($data);
        $data['isFinished'] = $flag;
        $data['status'] = self::BOM_STATUS_PUTTING;
        $this->file->write($data);
        $lock->unlock();
        $bomData = BomService::getBomInfo($bomId);
        $this->logger = LoggerFactory::getLogger('output');
        //如果是边装车边出库的话
        $carData = null;
        if ($bomData->getData('is_composite')) {
            $carService = new CarService();
            $result = $carService->getCarPosition($bomId, $reelId, $storageUuid);
            if ($result['status'] <= 0) {
                $this->logger->error("用户：{$nickname}，reelId：{$reelId}获取装车站位失败，失败的主要原因reelId不匹配");
            }
            $carData = $result['data'];
        }
        if (empty($leftMateriel)) {
            if ($flag == false) {
                //不需要归还物料
                return $this->resultTemplate(1, $this->returnTemplate($data['performData'], $data['batchData'], $data['lackData'], $data['feedData'], $leftMateriel, $carData));
            } else {
                //结束备料，并且不需要还余料
                $this->complete($bomId, $storageUuid);
                return $this->resultTemplate(2, $this->returnTemplate(null, null, null, null, null, $carData));
            }
        } else {
            //需要归还物料
            return $this->resultTemplate(3, $this->returnTemplate($data['performData'], $data['batchData'], $data['lackData'], $data['feedData'], $leftMateriel, $carData));
        }
    }

    /**
     * 完成备料
     * 1. 初始化文件信息
     * 2. 获取委外厂商信息
     * 3. 处理出库数据和特殊物料数据（不需要实物），进行判断该订单是欠料还是缺料
     * 4. 备份订单文件
     * 5. 记录出库物料数据
     * 6. 删除出库文件，清除仓库占用字段
     * 7. 返回数据
     *
     * @param $bomId
     * @param $storageUuid
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function complete($bomId, $storageUuid)
    {
        $this->logger->info("bomId：{$bomId}，进行完成备料处理");
        $this->initFile($bomId, $storageUuid);
        $bomInfo = BomService::getBomInfo($bomId);
        $data = $this->jsonDecode($this->file->read());
        if (empty($data) || (empty($data['recordData']) && empty($data['specialData']))) {
            return $this->resultTemplate(-1);
        }
        $supplierModel = new SupplierModel();
        $supplier = $supplierModel->findByUuid($bomInfo->getData('supplier_uuid'));
        $supplierId = $supplier->getData('supplier_id');
        $this->handleComplete($bomId, $data, $supplierId, $bomInfo->getData('uuid'));

        $backFile = new CommonFile($supplierId, Constant::RECORD_BACKUP_OUTPUT . $bomId . Constant::RECORD_FILE_SUFFIX);
        $data['status'] = self::BOM_STATUS_COMPLETED;
        $backFile->appendWrite($data);
        //记录出库数量
        $this->record($bomId, $storageUuid, $data);
        //删除文件
        $this->file->delete();
        StorageService::setStorageWithBomUuid('', $storageUuid);
        $this->logger->info('备料完成');
        return $this->resultTemplate(1);
    }

    /**
     * 错料归还
     *
     * @param $bomId
     * @param $reelId
     * @param $seatCode
     * @param $containerCode
     * @param $storageUuid
     * @return array
     * @throws Exception
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function revertWrongMateriel($bomId, $reelId, $seatCode, $containerCode, $storageUuid)
    {
        $this->initFile($bomId, $storageUuid);
        $data = $this->jsonDecode($this->file->read());

        $reelsModel = new ReelsModel();
        $reelInfo = $reelsModel->findReelByIdAndUuid($reelId, $storageUuid);
        $code = $reelInfo->getData('seat_code');
        if ($seatCode != $code) {
            return $this->resultTemplate(0);
        }
        $seatModel = new SeatModel();
        $seatId = $seatModel->findSeatIdByCodeAndStorageUuid($code, $storageUuid);
        if (empty($containerCodes)) {
            //如果是盘料，直接灭灯
            SocketService::send($seatId, $storageUuid, Constant::TURN_OFF_LIGHT);
        } else {
            //判断这个容器里的物料是否全部出库完成，全部出库完成的话需要灭灯
            $containerCodes = array_column($data['batchData'], 'containerCode');
            $index = array_search($containerCode, $containerCodes);
            if ($data['batchData'][$index]['isFinished']) {
                SocketService::send($seatId, $storageUuid, Constant::TURN_OFF_LIGHT);
            } else {
                SocketService::send($seatId, $storageUuid, Constant::TURN_ON_RED);
            }
        }
        return $this->resultTemplate(1, ['isFinished' => $data['isFinished']]);
    }

    /**
     * 余料归还
     *
     * @param $bomId
     * @param $reelId
     * @param $seatCode
     * @param $containerCode
     * @param $storageUuid
     * @param $count
     * @param $nickname
     * @return array
     * @throws Exception
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function revertLeftMateriel($bomId, $reelId, $seatCode, $containerCode, $storageUuid, $count, $nickname)
    {
        //因为余料归还是在错料归还有的基础上进行添加一些功能的，所以这里直接调用错料归还
        $result = $this->revertWrongMateriel($bomId, $reelId, $seatCode, $containerCode, $storageUuid);
        if ($result['status'] <= 0) {
            return $result;
        }
        $reelsModel = new ReelsModel();
        $reelInfo = $reelsModel->findReelByIdAndUuid($reelId, $storageUuid)->toArray();
        $reelInfo['count'] = $count;
        $inflow = InflowService::generateInflow($reelInfo, $nickname, '出库时余料入库');
        $inflowModel = new InflowModel();
        $res = $inflowModel->add($inflow);
        $isFinished = $result['data']['isFinished'];
        if (empty($res)) {
            //添加记录失败
            return $this->resultTemplate(-1);
        } else {
            if ($isFinished) {
                // 归还余料后，就是结束出库了
                $this->complete($bomId, $storageUuid);
                return $this->resultTemplate(2);
            } else {
                return $this->resultTemplate(1);
            }
        }
    }

    /**
     * 处理特殊的物料
     *
     * @param $bomId
     * @param $data
     * @param $supplierId
     */
    protected function handleSpecialMateriel($bomId, $data, $supplierId)
    {
        if (empty($data)) {
            return;
        }
        $logStr = "计算特殊库存===>出库单：" . $bomId . "厂商：" . $supplierId . "，物料号：%s,原始库存：%d，现在库存：%d，扣除/增加库存：%d";
        $supplierMaterielModel = new SupplierMaterielModel();
        foreach ($data as $materielId => $item) {
            $specialMap['supplier_id'] = $supplierId;
            $specialMap['sub_materiel_id'] = $materielId;
            $supplierMateriel = $supplierMaterielModel->findByIdAndMaterielId($supplierId, $materielId);
            //这里肯定是要有数据才能扣除处理的
            $supplierMaterielData = $supplierMateriel->getData();
            if (!empty($supplierMateriel) && !empty($supplierMaterielData)) {
                //库存数量减去投料数量
                $oldDbCount = $supplierMaterielData['count'];
                $subCount = $item['originalCount'];
                $newDbCount = $oldDbCount - $subCount;
                $supplierMaterielModel->updateCount($supplierMateriel->getData('uuid'), ['count' => $newDbCount]);
                $this->logger->debug(sprintf($logStr, $materielId, $oldDbCount, $newDbCount, -$subCount));
            }
        }
    }

    /**
     * 判断物料是否都已经完成
     *
     * @param $data
     * @return bool
     */
    protected function isFinished($data)
    {
        $flag = true;
        $isFinishList = array_unique(array_column($data['performData'], 'isFinished'));
        $flag = $flag && !in_array(false, $isFinishList);
        if (!$flag) {
            //如果是false 的话，直接返回了。容器就不需要查找了
            return false;
        }
        $isFinishList = array_unique(array_column($data['batchData'], 'isFinished'));
        $flag = $flag && !in_array(false, $isFinishList);

        return $flag;
    }

    /**
     * 判断出库的物料是否已经出足够了
     *
     * @param $reelId
     * @param $containerCode
     * @param $data
     * @return array
     */
    protected function isEnoughMateriel($reelId, $containerCode, $data)
    {
        if (empty($containerCode)) {
            $reelIds = array_column($data['performData'], 'reelId');
            $index = array_search($reelId, $reelIds);
            if ($index !== false) {
                $flag = $data['performData'][$index]['isFinished'];
                if ($flag == true) {
                    return $this->resultTemplate(-2);
                }
            }
        } else {
            $batchId = substr($reelId, self::REEL_ID_SLIDE_START, self::REEL_ID_SLIDE_LEN);
            foreach ($data['batchData'] as $item) {
                if ($batchId == $item['batchId'] && $containerCode == $item['containerCode']) {
                    if ($item['tempCount'] >= $item['count']) {
                        $this->logger->info("批次号：{$batchId}已经完成");
                        return $this->resultTemplate(-1);
                    }
                }
            }
        }
        return $this->resultTemplate(1);

    }

    /**
     * 处理bom单的数据
     *
     * @param $bomId
     * @param $bomUuid
     * @param $materielInfo
     * @param $recordData
     * @param $specialData
     * @param $storageUuid
     * @return array
     * @throws Exception
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    protected function handleCommonBomData($bomId, $bomUuid, $materielInfo, $recordData, $specialData, $storageUuid)
    {
        $reels = $this->getReels($materielInfo, $storageUuid);
        $result = $this->handleReels($reels, $storageUuid);
        $feedData = $this->handleFeedData($materielInfo, $recordData);
        $fileData = $this->fileTemplate($recordData, $result['reelData'], $result['batchData'], $result['lackData'], $feedData, $specialData, $result['seatIds']);
        return $this->afterHandleBom($fileData, $bomId, $bomUuid, $storageUuid);
    }

    /**
     * 处理特殊的bom订单
     *
     * @param $bomId
     * @param $bomUuid
     * @param $storageUuid
     * @return array
     * @throws Exception
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    protected function handleSpecialBomData($bomId, $bomUuid, $storageUuid)
    {
        $reels = $this->getReelsFromFile($bomId, $storageUuid);
        if (empty($reels['result'])) {
            return $this->resultTemplate(-3);
        }
        $result = $this->handleSpecialBomReels($reels, $storageUuid);
        $fileData = $this->fileTemplate($result['recordData'], $result['reelData'], $result['batchData'], $result['lackData'], $result['feedData'], [], $result['seatIds']);
        return $this->afterHandleBom($fileData, $bomId, $bomUuid, $storageUuid);
    }

    /**
     * 最后处理bom单，标志位的占用，以及文件的记录，bom单是否边出库边装车
     *
     * @param $fileData
     * @param $bomId
     * @param $bomUuid
     * @param $storageUuid
     * @return array
     * @throws Exception
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    protected function afterHandleBom($fileData, $bomId, $bomUuid, $storageUuid)
    {
        if ($this->isEmpty($fileData['performData']) && $this->isEmpty($fileData['batchData'])) {
            $this->logger->debug("出库单：{$bomId},全部都是缺料.bomUuid不记录到storage中");
        } else {
            $this->logger->debug("出库单：{$bomId}将bomUuid记录到storage中");
            StorageService::setStorageWithBomUuid($bomUuid, $storageUuid);
        }

        //如果是需要边入库边装车的话，进行数据判断处理
        $carData = null;
        if ($this->isComposite) {
            $this->logger->debug("出库单：{$bomId}需要边出库边装车");
            $bomModel = new BomModel();
            //更新数据库，将is_composite字段设置为边入库边装车
            $bomModel->updateBom($bomUuid, ['is_composite' => 1]);
            $carService = new CarService();
            $result = $carService->prepareCarSeatData($fileData['performData'], $bomId, $storageUuid);
            if ($result['status'] <= 0) {
                //输出日志，说明进行预分配站位信息失败
                $this->logger->error("出库单：{$bomId}在分配站位中出现错误，主要原因：没有数据");
            }
            $carData = $result['data'];
        }
        //出库获取物料的时间比较久，防止这样的操作
        //A先扫描，B后扫描，A先结束并且快速确认，并请求进行出库。
        //这个时候B，在覆盖文件的话，之前出库的物料会不见。所以需要以下操作操作，在一定程度上避免这种问题。
        //如果是获取出库单时间很短的话，这种情况发生的概率很低。如果时间长的话就容易发生。
        //也不见得20s就是对的。也有可能会其他特殊的情况出现。
        if ($this->file->isExist()) {
            $now = time();
            $oldFileData = $this->jsonDecode($this->file->read());
            $oldCreateTime = strtotime($oldFileData['createTime']);
            $this->logger->debug("出库文件已经存在,orderTime：{$oldCreateTime},nowTime：{$now}，orderStatus：{$oldFileData['status']}");
            if ($now <= $oldCreateTime + 20 && $oldFileData['status'] != self::BOM_STATUS_CREATED) {
                $this->logger->debug("bom单：{$bomId}扫描操作间隔时间小于20s，从旧的文件数据中获取出库信息");
                return $this->resultTemplate(1, $this->returnTemplate($oldFileData['performData'], $oldFileData['batchData'], $oldFileData['lackData'], $oldFileData['feedData'], null, $carData));
            }
        }
        $this->file->write($fileData);
        Debug::remark("getBomDataEnd");
        $time = Debug::getRangeTime('getBomDataStart', 'getBomDataEnd', 6) . 's';
        $this->logger->debug("获取物料==========结束（耗时：{$time}）============");
        return $this->resultTemplate(1, $this->returnTemplate($fileData['performData'], $fileData['batchData'], $fileData['lackData'], $fileData['feedData'], null, $carData));
    }

    /**
     * 处理投料数据
     *
     * @param $materielInfo
     * @return array
     */
    protected function handleFeedData($materielInfo, $recordData)
    {
        $feedData = [];
        foreach ($recordData as $key => $item) {
            $isMin = isset($item['isMinQuantity']) && $item['isMinQuantity'] == true ? true : false;
            $feedData[] = FileDataTemplate::outputFeedData($key, $item['originalCount'], $item['planCount'], $item['storeCount'], $isMin);
        }
        return $feedData;
    }

    /**
     * 是否是特殊的bom单
     *
     * @param $bomId
     * @param $operateType
     * @return bool
     */
    protected function isSpecialBom($bomId, $operateType)
    {
        preg_match('/\w[A-z]+/', $bomId, $arr);
        //退料的情况，直接去文件中reelId就行
        if ($arr[0] == 'BATCH' || $arr[0] == 'SMT' || in_array($operateType, [3, 4, 5])) {
            return true;
        }
        return false;
    }

    /**
     * 从文件中获取reels的数据
     *
     * @param $bomId
     * @param $storageUuid
     * @return array
     */
    protected function getReelsFromFile($bomId, $storageUuid)
    {
        $path = Constant::RECORD_RETURN_REELS . $bomId . Constant::RECORD_FILE_SUFFIX;
        $file = new CommonFile($storageUuid, $path);
        $data = $file->read();
        if (empty($data)) {
            return null;
        } else {
            return json_decode($data, true);
        }
    }

    protected function getReels($materielInfo, $storageUuid)
    {
        Debug::remark("getReelsStart");
        $this->logger->debug("获取reelId==========开始============");
        $result = [];
        $lack = [];
        $materielIds = array_column($materielInfo, 'sub_materiel_id');
        ksort($materielIds);
        $reelsModel = new ReelsModel();
        $reels = $reelsModel->queryReelsByMaterielIds($materielIds, $storageUuid);
        $keys = array_column($reels, 'sub_materiel_id');
        foreach ($materielInfo as $item) {
            $count = 0;
            $start = array_search($item['sub_materiel_id'], $keys);
            $tmpReels = array_slice($reels, $start);
            foreach ($tmpReels as $value) {
                if ($value['sub_materiel_id'] == $item['sub_materiel_id']) {
                    if ($item['count'] > $count) {
                        $count += $value['count'];
                        array_push($result, $value);
                    } else {
                        break;
                    }
                }
            }
            //把缺料的记录下来
            if ($count != $item['count']) {
                $lack[] = [
                    //需要的数量减去拥有的数量
                    'count' => $item['count'] - $count,
                    'sub_materiel_id' => strtoupper($item['sub_materiel_id'])
                ];
            }
        }
        Debug::remark("getReelsEnd");
        $time = Debug::getRangeTime('getReelsStart', 'getReelsEnd', 6) . 's';
        $this->logger->debug("获取reelId==========结束（耗时：{$time}）============");
        return ['result' => $result, 'lack' => $lack];
    }

    protected function handleReels($info, $storageUuid)
    {
        Debug::remark("handleReelsStart");
        $this->logger->debug("处理reelId==========开始============");
        $batchData = [];//容器上的料
        $reelData = [];//盘料
        $result = [];//缺料返回的数据
        $tempContainer = [];//暂时保存容器号相同的数据
        $reels = $info['result'];
        $lack = $info['lack'];
        $seatModel = new SeatModel();
        foreach ($reels as $item) {//将数据分类
            if (empty($item['container_code'])) {//盘料的数据
                $reelData[] = FileDataTemplate::outputReelData($item['reel_id'], $item['seat_code'], $item['sub_materiel_id'], $item['count']);
            } else {
                //批次号
                $batchId = substr($item['reel_id'], self::REEL_ID_SLIDE_START, self::REEL_ID_SLIDE_LEN);
                if (!empty($tempContainer[$item['container_code']])) {//查看一下该容器号下有没有数据
                    if (!empty($tempContainer[$item['container_code']][$batchId])) {//批次号里有没有数据
                        $tempContainer[$item['container_code']][$batchId]['count'] += $item['count'];//相同批次号数据相加
                    } else {//将这个批次号记录起来
                        $tempContainer[$item['container_code']][$batchId] = FileDataTemplate::outputBatchData($batchId, $item['seat_code'],
                            $item['container_code'], $item['sub_materiel_id'], $item['count']);
                    }
                } else {//将这个容器的批次号记录起来
                    $tempContainer[$item['container_code']][$batchId] = FileDataTemplate::outputBatchData($batchId, $item['seat_code'],
                        $item['container_code'], $item['sub_materiel_id'], $item['count']);
                }
            }
        }
        foreach ($tempContainer as $key => $value) {
            foreach ($value as $it) {
                $batchData[] = $it;
            }
        }

        //获取seatId
        $seatCodes = array_column($reels, 'seat_code');
        $seatIds = $seatModel->querySeatId($seatCodes, $storageUuid);
        $seatIds = array_merge(array_filter(array_unique($seatIds)));//过滤掉值能转为false的值，并且合并亮灯信息,并且重新排序
        foreach ($lack as $item) {
            if ($item['count'] > 0) { //大于0表示欠料，欠料信息要给pda，但是多给的情况不用
                $result[] = [
                    'materielId' => $item['sub_materiel_id'],
                    'lackNum' => $item['count']
                ];
            }
        }
        Debug::remark("handleReelsEnd");
        $time = Debug::getRangeTime('handleReelsStart', 'handleReelsEnd', 6) . 's';
        $this->logger->debug("处理reelId==========结束（耗时：{$time}）============");
        return [
            'batchData' => $batchData,
            'reelData' => $reelData,
            'seatIds' => $seatIds,
            'lackData' => $result
        ];
    }

    /**
     * 处理特殊的bom单的内容，即该出库单的内容全部精确到物料出库
     *
     * @param $reels
     * @param $storageUuid
     * @return array
     */
    protected function handleSpecialBomReels($reels, $storageUuid)
    {
        $reelData = [];//盘料
        $seatIds = [];//亮灯的数据
        $seatModel = new SeatModel();
        $recordData = [];
        $feedData = [];
        foreach ($reels as $item) {//将数据分类
            $reelData[] = FileDataTemplate::outputReelData($item['reelId'], $item['seatCode'], $item['materielId'], $item['count']);
            //获取record数据
            if (empty($recordData[$item['materielId']])) {
                $recordData[$item['materielId']] = FileDataTemplate::outputRecordData($item['count'], $item['count']);
            } else {
                $recordData[$item['materielId']]['originalCount'] += $item['count'];
                $recordData[$item['materielId']]['planCount'] += $item['count'];
            }
            //获取feed数据
            if (empty($feedData[$item['materielId']])) {
                $recordData[$item['materielId']] = FileDataTemplate::outputFeedData($item['materielId'], $item['count']);
            } else {
                $recordData[$item['materielId']]['count'] += $item['count'];
            }
            //获取seat_id
            $tmpSeatId = $seatModel->findSeatIdByCodeAndStorageUuid($item['seatCode'], $storageUuid);
            array_push($seatIds, $tmpSeatId);
        }
        $seatIds = array_merge(array_filter(array_unique($seatIds)));//过滤掉值能转为false的值，并且合并亮灯信息,并且重新排序
        return [
            'batchData' => [],
            'reelData' => $reelData,
            'seatIds' => $seatIds,
            'lackData' => [],
            'recordData' => $recordData,
            'feedData' => array_values($feedData)
        ];
    }

    /**
     * 获取缺料出库单的物料信息
     *
     * @param $bomUuid
     * @param $supplierId
     * @return mixed
     */
    abstract public function getLackMateriel($bomUuid, $supplierId);

    /**
     * 获取出库单的物料信息
     *
     * @param $bomUuid
     * @param $supplierId
     * @return mixed
     */
    abstract public function getBomMateriel($bomUuid, $supplierId);

    /**
     * 处理完成的时候的接口
     *
     * @param $bomId
     * @param $data
     * @param $supplierId
     * @param $bomUuid
     * @return mixed
     */
    abstract public function handleComplete($bomId, $data, $supplierId, $bomUuid);

    private function initFile($bomId = '', $storageUuid)
    {
        $filePath = Constant::RECORD_OUTPUT . $bomId . Constant::RECORD_FILE_SUFFIX;
        $this->file = new CommonFile($storageUuid, $filePath);
        $this->storageUuid = $storageUuid;
    }

    private function record($bomId, $storageUuid, $data)
    {
        if (!empty($data)) {
            $file = new CommonFile($storageUuid, Constant::RECORD_OUTPUT_COUNT . $bomId . Constant::RECORD_FILE_SUFFIX);
            $oldData = $this->jsonDecode($file->read());
            if (!empty($oldData)) {
                $finalData = $this->merge($oldData, $data);
            } else {
                $finalData = $this->badge($data);
            }
            $file->write($finalData);
        }
    }

    private function merge($oldData, $newData)
    {
        $finalData = $oldData;
        $recordData = $newData['recordData'];
        foreach ($recordData as $key => $datum) {
            //投料数不能改变
            $tempCount = $finalData[$key]['originalCount'];
            $tempPlanCount = $finalData[$key]['planCount'];
            $tempCurrCount = $finalData[$key]['oughtCount'];
            $finalData[$key] = [
                'originalCount' => $tempCount,
                'planCount' => $tempPlanCount,
                'oughtCount' => $datum['currentCount'] + $tempCurrCount
            ];
        }
        return $finalData;
    }

    private function badge($data)
    {
        $finalData = [];
        $recordData = $data['recordData'];
        foreach ($recordData as $key => $value) {
            $finalData[$key] = [
                'originalCount' => $value['originalCount'],
                'planCount' => $value['planCount'],
                'oughtCount' => $value['currentCount']
            ];
        }
        $specialData = $data['specialData'];
        foreach ($specialData as $key => $datum) {
            $finalData[$key] = [
                'originalCount' => $datum['originalCount'],
                'planCount' => 0,
                'oughtCount' => $datum['actualCount'],
            ];
        }
        return $finalData;
    }

    /**
     * 返回预先处理物料的数据模板
     *
     * @param array $materielData
     * @param array $recordData
     * @param array $specialData
     * @return array
     */
    protected function returnPreviousProcessMaterielTemplate($materielData = [], $recordData = [], $specialData = [])
    {
        if ($materielData instanceof Collection) {
            $materielData = $materielData->toArray();
        }
        return [
            'materielData' => $materielData,
            'recordData' => $recordData,
            'specialData' => $specialData
        ];
    }

    /**
     * 返回给pda的模板
     * @param array $preformList
     * @param array $batchList
     * @param array $lackInfoList
     * @param array $planFeedList
     * @param null $returnMaterial
     * @return array
     */
    private function returnTemplate($preformList = [], $batchList = [], $lackInfoList = [], $planFeedList = [], $returnMaterial = null, $carData = null)
    {
        $ret = [
            'performList' => $preformList,
            'batchList' => $batchList,
            'lackInfoList' => $lackInfoList,
            'planFeedList' => $planFeedList,
            'returnMaterial' => $returnMaterial,
            'carData' => $carData
        ];
        return $ret;
    }

    /**
     * 写入文件的模板
     * @param array $recordData
     * @param array $performData
     * @param array $batchData
     * @param array $lackData
     * @param array $feedData
     * @param array $specialData
     * @param array $seatIds
     * @param bool $isFinished
     * @return array
     */
    private function fileTemplate($recordData = [], $performData = [], $batchData = [], $lackData = [], $feedData = [], $specialData = [], $seatIds = [], $isFinished = false)
    {
        return [
            'recordData' => $recordData,
            'performData' => $performData,
            'batchData' => $batchData,
            'lackData' => $lackData,
            'feedData' => $feedData,
            'specialData' => $specialData,
            'seatIds' => $seatIds,
            'isFinished' => $isFinished,
            'person' => 0,
            'status' => self::BOM_STATUS_CREATED,
            'createTime' => get_current_time()
        ];
    }
}
